import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.AbstractAction;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.LayoutStyle;
import javax.swing.WindowConstants;
import javax.swing.border.LineBorder;


/**
 * @author Matthew Lingelbach
 * @version 1.0
 * 
 * Controls the game
 */
public class Board extends javax.swing.JFrame {
	/* GUI Components */
	private JMenuBar menu;
	private JMenuItem Help;
	private JPanel tiles; 					// Contains the tiles
	private JButton start;
	private JPanel optionsPanel;
	private AbstractAction closeHelp;
	private JTextPane helpText;
	private JButton helpOK;
	private JDialog helpDialog;
	private AbstractAction helpAction;
	private JMenu options;
	/* End GUI Components */

	private Tile[][] tileList;				// Tiles/grid squares
	private int[][] currentState;
	private boolean running = false;		// Is the game currently running?
	private boolean playersTurn = true;
	private boolean metPlayer = false;

	private JPanel tilePanel;
	private JLabel player;

	private int[] playerPos = new int[2];
	private int[] compPos = new int[2];
	private int strat = 0;

	int x, y;
	private boolean atMiddle = false;
	private boolean noStrat = true;

	/**
	 * Sets the window title and initializes with a method call
	 */
	public Board(int x, int y) {
		super("Traffic Jam");
		this.x = x; this.y = y;
		initGUI();
	}

	/**
	 * The bulk of the GUI setup
	 */
	private void initGUI() {
		try {
			setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
			getContentPane().setBackground(new java.awt.Color(192,192,192));
			tilePanel = getTiles();
			getContentPane().add(getOptionsPanel(), BorderLayout.SOUTH);
			getContentPane().add(tilePanel, BorderLayout.CENTER);
			{
				menu = new JMenuBar();
				setJMenuBar(menu);
				{
					options = new JMenu();
					menu.add(options);
					options.setText("Options");
					{
						Help = new JMenuItem();
						options.add(Help);
						Help.setText("Help");
						Help.setAction(getHelpAction());
					}
				}
			}
			pack();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}


	/**
	 * Sets up the tiles surrounding JPanel and creates each tile
	 * @return
	 */
	private JPanel getTiles() {
		if(tiles == null) {
			tiles = new JPanel();
			tiles.setLayout(null);
			tiles.setBackground(new java.awt.Color(192,192,192));	
			if(x > 20 && y > 20)
				tiles.setPreferredSize(new java.awt.Dimension(x*10, y*10));
			else if(x > 20)
				tiles.setPreferredSize(new java.awt.Dimension(x*10, 200));
			else if(y > 20)
				tiles.setPreferredSize(new java.awt.Dimension(200, y*10));
			else
				tiles.setPreferredSize(new java.awt.Dimension(200, 200));
			tileList = new Tile[y][x];			
			currentState = new int[y][x];
			//			nextState = new int[x][y];
			int x=1; int y=1;
			if(this.x < 20)
				x = (20-this.x-1)*10/2;
			if(this.y < 20)
				y = (20-this.y-1)*10/2;

			for(int i = 0; i < this.y; i++){
				for(int j = 0; j < this.x; j++){
					Tile c = new Tile(x, y, i, j);
					c.addMouseListener(new Toggle());
					tileList[i][j]=c;
					currentState[i][j] = 0;
					tiles.add(c);						
					x+=10;
				}
				x=1;
				if(this.x < 20)
					x = (20-this.x-1)*10/2;
				y+=10;
			}
		}
		return tiles;
	}

	/**
	 * Decides how the computer should make its next move.
	 * Uses a Mimic strategy on even sided boards.
	 * 5x5 Grid checks the players location and moves accordinly. 
	 */
	private void makeMove(){
		if(this.x %2 == 0 || this.y%2 == 0 || metPlayer){
			int pX = playerPos[1];
			int pY = playerPos[0];
			int newX = 0, newY = 0;
			newX = this.x - 1 - pX;
			newY = this.y - 1 - pY;
			tileList[newY][newX].setClicked(true, 1);
			tileList[newY][newX].draw();
			currentState[newY][newX] = 1;
			compPos[1] = newX;
			compPos[0] = newY;
		}else{ // Only works for 5x5
			int newX = compPos[1], newY = compPos[0];
			int pX = playerPos[1], pY = playerPos[0];
			int cX = newX, cY = newY;
			
			// First move to the middle in the same fashion no matter what
			if((compPos[0] != this.y/2 || compPos[1] != this.x/2) && !atMiddle){
				if(compPos[0] == this.y - 1 && compPos[1] == this.x - 1)
					newX -= 1;
				else if(compPos[0] != this.y/2)
					newY -= 1;
				else
					newX -= 1;
				if(newX == this.x/2 && newY == this.y/2)
					atMiddle = true;
			}
			
			// Develop a strategy based on the player position
			if(atMiddle && noStrat){
				if(pX == newX - 1 && pY == newY){
					strat = 1;
				}else if(pX == newX && pY == newY - 1){
					strat = 2;
				}else if(newY - pY  >  newX - pX){
					strat = 3;
				}else if(newX - pX > newY - pY){
					strat = 4;
				}
				noStrat = false;
			}else if(strat == 1){
				System.out.println("Strat 1");
				int[] pos = leftUpDownRight();
				newY = pos[0]; newX = pos[1];
			}else if(strat == 2){
				System.out.println("Strat 2");
				int[] pos = upLeftDownRight();
				newY = pos[0]; newX = pos[1];
			}else if(strat == 3){
				System.out.println("Strat 3");
				int[] pos = upLeftDownRight();
				newY = pos[0]; newX = pos[1];
			}else if(strat == 4){
				System.out.println("Strat 4");
				int[] pos = leftUpDownRight();
				newY = pos[0]; newX = pos[1];
			}
			tileList[newY][newX].setClicked(true, 1);
			tileList[newY][newX].draw();
			currentState[newY][newX] = 1;
			compPos[1] = newX;
			compPos[0] = newY;
		}
		playersTurn = true;
		checkWin();
	}
	
	// Uses a follow the wall strategy
	private int[] upLeftDownRight() {
		int cX = compPos[1], cY = compPos[0];
		System.out.println(cX+" "+ cY);
		int[] pos = new int[2];
		if(cY - 1 >= 0){ //Up
			System.out.println("Up");
			if(!tileList[cY-1][cX].isClicked()){
				pos[0] = cY-1;
				pos[1] = cX;
				return pos;
			}
		}
		if(cX - 1 >= 0){ //Left
			System.out.println("Left");
			if(!tileList[cY][cX-1].isClicked()){
				pos[0] = cY;
				pos[1] = cX-1;
				return pos;
			}
		}
		if(cY + 1 < this.y){ //Down
			System.out.println("Down");
			if(!tileList[cY+1][cX].isClicked()){
				pos[0] = cY+1;
				pos[1] = cX;
				return pos;
			}
		}
		if(cX + 1 < this.x){ //Right
			System.out.println("Right");
			if(!tileList[cY][cX+1].isClicked()){
				pos[0] = cY;
				pos[1] = cX+1;
				return pos;
			}
		}

		System.out.println(pos[1]+" "+pos[0]);
		return pos;
	}

	private int[] leftUpDownRight() {
		int cX = compPos[1], cY = compPos[0];
		int pX = playerPos[1], pY = playerPos[0];
		int[] pos = new int[2];
		if(cX - 1 >= 0){ //Left
			if(!tileList[cY][cX-1].isClicked()){
				pos[0] = cY;
				pos[1] = cX-1;
				return pos;
			}
		}
		if(cY - 1 >= 0){ //Up
			if(!tileList[cY-1][cX].isClicked()){
				pos[0] = cY-1;
				pos[1] = cX;
				return pos;
			}
		}
		if(cY + 1 < this.y){ //Down
			if(!tileList[cY+1][cX].isClicked()){
				pos[0] = cY+1;
				pos[1] = cX;
				return pos;
			}
		}
		if(cX + 1 < this.x){ //Right
			if(!tileList[cY][cX+1].isClicked()){
				pos[0] = cY;
				pos[1] = cX+1;
				return pos;
			}
		}
		return pos;
	}

	/**
	 * Checks to see if either player has any moves remaining
	 * @return
	 */
	private boolean checkWin(){
		boolean test = false;
		String winner = "";
		int x = 0, y = 0, x1 = 0, x2 = 0, y1 = 0, y2 = 0;
		if(playersTurn){
			x = playerPos[1];
			y = playerPos[0];
			winner = "Computer";
		}else{
			x = compPos[1];
			y = compPos[0];
			winner = "Player";
		}
		x1 = x+1; 
		x2 = x-1;
		y1 = y+1;
		y2 = y-1;
		if(x2 >= 0){
			if(tileList[y][x2].isClicked())
				test = true;
			else return false;
		}else test = true;
		if(x1 < this.x){
			if(tileList[y][x1].isClicked())
				test = true;
			else return false;
		}else test = true;
		if(y2 >= 0){
			if(tileList[y2][x].isClicked())
				test = true;
			else return false;
		}else test = true;
		if(y1 < this.y){
			if(tileList[y1][x].isClicked())
				test = true;
			else return false;
		}else test = true;
		if(test){
			running = false;
			System.out.println("Game over. "+winner+" wins.");
		}
		return test;
	}

	/**
	 * Sets up the help display action
	 * @return The abstract action to start the help menu
	 */
	private AbstractAction getHelpAction() {
		if(helpAction == null) {
			helpAction = new AbstractAction("Help", null) {
				public void actionPerformed(ActionEvent evt) {
					getHelpDialogx().pack();
					getHelpDialogx().setLocationRelativeTo(null);
					getHelpDialogx().setVisible(true);
				}
			};
		}
		return helpAction;
	}

	/**
	 * Displays the help dialog to the user
	 * @return help dialog
	 */
	private JDialog getHelpDialogx() {
		if(helpDialog == null) {
			helpDialog = new JDialog(this);
			GroupLayout helpDialogLayout = new GroupLayout((JComponent)helpDialog.getContentPane());
			helpDialog.getContentPane().setLayout(helpDialogLayout);
			helpDialogLayout.setHorizontalGroup(helpDialogLayout.createParallelGroup()
					.addComponent(getHelpText(), GroupLayout.Alignment.LEADING, GroupLayout.PREFERRED_SIZE, 484, GroupLayout.PREFERRED_SIZE)
					.addGroup(GroupLayout.Alignment.LEADING, helpDialogLayout.createSequentialGroup()
							.addGap(342)
							.addComponent(getHelpOK(), GroupLayout.PREFERRED_SIZE, 84, GroupLayout.PREFERRED_SIZE)
							.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, 0, Short.MAX_VALUE)));
			helpDialogLayout.setVerticalGroup(helpDialogLayout.createSequentialGroup()
					.addComponent(getHelpText(), GroupLayout.PREFERRED_SIZE, 70, GroupLayout.PREFERRED_SIZE)
					.addGap(56)
					.addComponent(getHelpOK(), GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE));
		}
		return helpDialog;
	}

	/**
	 * Display the OK button to dispose the help dialog
	 * @return
	 */
	private JButton getHelpOK() {
		if(helpOK == null) {
			helpOK = new JButton();
			helpOK.setText("OK");
			helpOK.setBounds(342, 126, 84, 22);
			helpOK.setAction(getCloseHelp());
		}
		return helpOK;
	}

	/**
	 * The text in a text pane for the help dialog
	 * @return
	 */
	private JTextPane getHelpText() {
		if(helpText == null) {
			helpText = new JTextPane();
			helpText.setText("Use the mouse to select cells to be initially occupied, then press the Start button. \nTo pause the simulation, press Stop. \nTo end the simulation, press Reset. \nUse the Speed drop-down menu to select the generation period.");
			helpText.setBounds(0, 0, 1149, 70);
		}
		return helpText;
	}

	/**
	 * Disposes the help dialog
	 * @return
	 */
	private AbstractAction getCloseHelp() {
		if(closeHelp == null) {
			closeHelp = new AbstractAction("OK", null) {
				public void actionPerformed(ActionEvent evt) {
					helpDialog.dispose();
				}
			};
		}
		return closeHelp;
	}

	/**
	 * The bottom panel that contains Start/Stop, Reset, and Interval
	 * @return
	 */
	private JPanel getOptionsPanel() {
		if(optionsPanel == null) {
			optionsPanel = new JPanel();
			//			optionsPanel.setLayout(optionsPanelLayout);
			optionsPanel.setPreferredSize(new java.awt.Dimension(tilePanel.getWidth(), 47));
			optionsPanel.setBorder(new LineBorder(new java.awt.Color(0,0,0), 1, false));
			optionsPanel.add(getStart());
			optionsPanel.add(getPlayers());
		}
		return optionsPanel;
	}

	/**
	 * Displays the Start/Stop button
	 * @return
	 */
	private JButton getStart() {
		if(start == null) {
			start = new JButton();
			//			AnchorLayout startLayout = new AnchorLayout();
			//			start.setLayout(startLayout);
			start.setText("Start");
			start.setPreferredSize(new java.awt.Dimension(73, 22));
			start.addActionListener(new StartStop());
		}
		return start;
	}

	private JPanel getPlayers(){
		JPanel p = new JPanel();
		p.setLayout(new FlowLayout());
		p.add(new JLabel("Turn: "));
		player = new JLabel("Player");
		p.add(player);
		return p;
	}

	/**
	 * Determines if the player can move.
	 * @param t
	 * @return
	 */
	private boolean canMove(Tile t){
		int x = playerPos[1];
		int y = playerPos[0];
		int i = t.getI();
		int j = t.getJ();
		if(i == y+1 && j == x)
			return true;
		else if(i == y-1 && j == x)
			return true;
		else if(i == y && j == x+1)
			return true;
		else if(i == y && j == x-1)
			return true;
		return false;
	}

	/**
	 * Detects mouse clicks in the grid to toggle a circle
	 * in the tiles and updates the currentState accordingly.
	 * @author Matthew Lingelbach
	 */
	class Toggle implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent arg0) {
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
		}

		@Override
		public void mouseExited(MouseEvent arg0) {			
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			if(running){
				if(playersTurn){
					Tile t = ((Tile) arg0.getComponent());
					if(!t.isClicked() && canMove(t)){
						t.setClicked(true, 0);
						t.draw();
						currentState[t.getI()][t.getJ()] = 1;
						playerPos[1] = t.getJ();
						playerPos[0] = t.getI();

						playersTurn = false;
						checkWin();
						if(running)
							makeMove();
					}
				}
			}
		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
		}

	}



	/**
	 * Starts the game.
	 * @author Matthew Lingelbach
	 */
	class StartStop implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			JButton but = (JButton)e.getSource();
			String s = but.getText();
			if(s.equals("Start")){
				if(!running){
					running = true;
					playerPos[0] = 0;
					playerPos[1] = 0;
					tileList[0][0].setClicked(true, 0);
					tileList[0][0].draw();
					currentState[0][0] = 1;

					compPos[0] = y-1;
					compPos[1] = x-1;
					tileList[y-1][x-1].setClicked(true, 1);
					tileList[y-1][x-1].draw();
					currentState[y-1][x-1] = 1;

					if(x%2 != 0 && y%2 != 0){
						playersTurn = false;
						makeMove();
					}
				}
			}
		}
	}
}
